/**
 *  DoubleWordItem.java : IceBird project
 *  23:54:15 
 */
package icebird.compiler.ncomp.vs;

import sun.reflect.generics.reflectiveObjects.NotImplementedException;
import icebird.compiler.ncomp.CodeCompiler;
import icebird.compiler.ncomp.X86Assembler;
import icebird.compiler.ncomp.X86Register;
import icebird.compiler.ncomp.X86RegisterPool;
import icebird.compiler.ncomp.X86Register.GPR;
import icebird.metadata.BasicType;

/**
 * @author Sergey Shulepoff[Knott]
 */
public abstract class DoubleWordItem extends Item {

	/**
	 * @param l
	 */
	public DoubleWordItem(Location l) {
		super(l);
	}

	/* (non-Javadoc)
	 * @see icebird.compiler.ncomp.vs.Item#getCategory()
	 */
	public int getCategory() {
		// Double words(Like float and long) is second category.
		return 2;
	}

	/* (non-Javadoc)
	 * @see icebird.compiler.ncomp.vs.Item#clone()
	 */
	public final Item clone() {
		X86Assembler asm = X86Assembler.getAsm();
		
		// Get location.
		if (getLocation().isGPR()) {
			GPR lsb = CodeCompiler.getCompiler().request(BasicType.Int);
			GPR msb = CodeCompiler.getCompiler().request(BasicType.Int);
			
			GPR2Location gpr = (GPR2Location)getLocation();
			
			asm.move(lsb, gpr.getLW());
			asm.move(msb, gpr.getHW());
			
			return getGPRWord(lsb, msb, getType());
		}
		
		// Get constant
		if (getLocation().isConstant()) {
			return cloneConstant();
		}
		
		if (getLocation().isLocal()) {
			LocalLocation l = (LocalLocation)getLocation();
			return getLocal(l.getOffset(), getType());
		}
		
		throw new IllegalArgumentException("Invalid item kind");
	}
	
	/* (non-Javadoc)
	 * @see icebird.compiler.ncomp.vs.Item#load()
	 */
	public final void load() {
		if (getLocation().isGPR()) return;
		
		VirtualStack s = CodeCompiler.getCompiler().getVS();
		X86RegisterPool p = CodeCompiler.getCompiler().getPool();
		
		X86Register l = p.request(BasicType.Int);
		if (l == null) {
			s.push();
			l = p.request(BasicType.Int);
		}
		
		X86Register h = p.request(BasicType.Int);
		if (h == null) {
			s.push();
			l = p.request(BasicType.Int);
		}
		
		loadTo(l, h);
	}
	
	public final void loadTo(X86Register l, X86Register h) {
		X86Assembler asm = X86Assembler.getAsm();
		VirtualStack s = CodeCompiler.getCompiler().getVS();
		X86RegisterPool p = CodeCompiler.getCompiler().getPool();
		
		if (getLocation().isGPR()) {
			GPR2Location current = (GPR2Location)getLocation();
			
			if (h != current.getLW()) {
				if (h != current.getHW()) {
					asm.move(h, current.getHW());
					if (l != current.getHW()) {
						p.release(current.getHW());
					}
				} 
				
				if (l != current.getLW()) {
					asm.move(l, current.getLW());
					p.release(current.getLW());
				} 
			} else if (l != current.getHW()) {
				asm.move(l, current.getLW());
				asm.move(h, current.getHW());
				
				p.release(h);
			} else {
				// Just swap.
				asm.xchg(current.getHW(), current.getLW());
			}
		}
		
		if (getLocation().isLocal()) {
			int offset = ((LocalLocation)getLocation()).getOffset();
			
			asm.move(l, X86Register.EBP, offset);
			asm.move(h, X86Register.EBP, offset-4);
		}
		
		if (getLocation().isConstant()) {
			loadConstant((GPR)l, (GPR)h);
		}
		
		if (getLocation().isStack()) {
			asm.pop(l);
			asm.pop(h);
		}
		
		if (getLocation().isFPUStack()) {
			s.getFPUStack().pop(this);
			
			asm.lea(X86Register.ESP, X86Register.ESP, -8);
			popFPU(X86Register.ESP, 0);
			
			asm.pop(l);
			asm.pop(h);
		}
		
		setLocation(new GPR2Location((GPR)l, (GPR)h));
	}

	
	
	
	/* (non-Javadoc)
	 * @see icebird.compiler.ncomp.vs.Item#loadGPR()
	 */
	public final void loadGPR() {
		if (getLocation().isGPR()) return;
		
		VirtualStack s = CodeCompiler.getCompiler().getVS();
		X86RegisterPool p = CodeCompiler.getCompiler().getPool();
		
		X86Register l = p.request(BasicType.Int);
		if (l == null) {
			s.push();
			l = p.request(BasicType.Int);
		}
		
		X86Register h = p.request(BasicType.Int);
		if (h == null) {
			s.push();
			l = p.request(BasicType.Int);
		}
		
		loadTo(l, h);
	}

	/* (non-Javadoc)
	 * @see icebird.compiler.ncomp.vs.Item#push()
	 */
	public final void push() {
		VirtualStack s = CodeCompiler.getCompiler().getVS();
		X86Assembler asm = X86Assembler.getAsm();
		
		if (getLocation().isGPR()) {
			GPR2Location gpr = (GPR2Location)getLocation();
			
			asm.push(gpr.getLW());
			asm.push(gpr.getHW());
		}
		
		if (getLocation().isLocal()) {
			LocalLocation l = (LocalLocation)getLocation();
			
			asm.push(X86Register.EBP, l.getOffset());
			asm.push(X86Register.EBP, l.getOffset() - 4);
		}
		
		if (getLocation().isConstant()) {
			pushConstant();
		}
		
		if (getLocation().isFPUStack()) {
			FPUStack fp = s.getFPUStack();
			
			if (fp.getPeek() != this) {
				FPUStack.fxch(fp, fp.getRegister(this));
			}
			
			fp.pop(this);
			
			asm.lea(X86Register.ESP, X86Register.ESP, -8);
			popFPU(X86Register.ESP, 0);
		}
		
		cleanup();
		setLocation(new StackLocation());
	}

	/* (non-Javadoc)
	 * @see icebird.compiler.ncomp.vs.Item#pushFPU()
	 */
	public final void pushFPU() {
		VirtualStack s = CodeCompiler.getCompiler().getVS();
		X86Assembler asm = X86Assembler.getAsm();
		
		if (getLocation().isGPR()) {
			GPR2Location gpr = (GPR2Location)getLocation();
			
			asm.push(gpr.getLW());
			asm.push(gpr.getHW());
			
			popFPU(X86Register.ESP, 0);
			asm.lea(X86Register.ESP, X86Register.ESP, 8);
		}
		
		if (getLocation().isLocal()) {
			LocalLocation l = (LocalLocation)getLocation();
			
			pushFPU(X86Register.EBP, l.getOffset());
		}
		
		if (getLocation().isConstant()) {
			pushConstant();
			
			pushFPU(X86Register.ESP, 0);
			asm.lea(X86Register.ESP, X86Register.ESP, 8);
		}
		
		if (getLocation().isFPUStack()) {
			s.getFPUStack().pop(this);
			s.getFPUStack().push(this);
		}
		
		if (getLocation().isStack()) {
			pushFPU(X86Register.ESP, 0);
			asm.lea(X86Register.ESP, X86Register.ESP, 8);
		}
		
		cleanup();
		setLocation(new FPULocation());
		s.getFPUStack().push(this);
	}

	/* (non-Javadoc)
	 * @see icebird.compiler.ncomp.vs.Item#loadSSE()
	 */
	public void loadSSE() {
		throw new NotImplementedException();
	}

	/* (non-Javadoc)
	 * @see icebird.compiler.ncomp.vs.Item#uses(icebird.compiler.ncomp.X86Register)
	 */
	public boolean uses(X86Register r) {
		if (getLocation().isGPR()) {
			GPR2Location l = (GPR2Location)getLocation();
			return (l.getLW() == r) || (l.getHW() == r);
		}
		
		return false;
	}
	
	// Free registers
	private final void cleanup() {
		X86RegisterPool p = CodeCompiler.getCompiler().getPool();
		
		if (getLocation().isGPR()) {
			GPR2Location l = (GPR2Location)getLocation();
			
			p.release(l.getLW());
			p.release(l.getHW());
		}
	}

	/* (non-Javadoc)
	 * @see icebird.compiler.ncomp.vs.Item#release()
	 */
	public void doRelease() {
		cleanup();
	}

	protected abstract DoubleWordItem cloneConstant();
	protected abstract void loadConstant(GPR lsb, GPR msb);
	protected abstract void pushConstant();
	
	protected abstract void popFPU(GPR r, int disp);
	protected abstract void pushFPU(GPR r, int disp);
	
	/**
	 * Get word item located on GPR.
	 * @return WordItem
	 */
	public static DoubleWordItem getGPRWord(GPR lsb, GPR msb, BasicType type) {
		GPR2Location loc = new GPR2Location(lsb, msb);
		if (type == BasicType.Double) {
			return new DoubleItem(loc);
		} else {
			return new LongItem(loc);
		}
	}
	
	/**
	 * Low word
	 * @return DoubleWordItem
	 */
	public static DoubleWordItem getLocal(int offset, BasicType type) {
		LocalLocation loc = new LocalLocation(offset);
		if (type == BasicType.Double) {
			return new DoubleItem(loc);
		} else {
			return new LongItem(loc);
		}
	}
}
